/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package br.com.tutotec.vc;

import br.com.tutotec.vc.erros.EArquivoInvalido;
import java.io.File;
import java.io.IOException;
import static org.bytedeco.javacpp.opencv_core.*;
import static org.bytedeco.javacpp.opencv_highgui.*;
import static org.bytedeco.javacpp.opencv_imgproc.*;
import static org.bytedeco.javacpp.opencv_objdetect.*;


/**
 *
 * @author Acacio
 */
public class PreProcessamentoImagem {
    
    private CvHaarClassifierCascade classificadorCascata;
    private CvMemStorage storage;
    private String arquivoHaar;
    private boolean ConverteEscalaCinza;
    private boolean TrataHistogram;
    private CvSeq facesEncontradas;
    private File dirSaida;
    private float[][] matrizKernel;
    
    public static final int DIM_MAX = 1000;

    public PreProcessamentoImagem(String HaarFile, boolean ConverteEscalaCinza, boolean TrataHistogram, float[][] matrizKernel) throws IOException {
        this.Inicializar(HaarFile, ConverteEscalaCinza, TrataHistogram, matrizKernel);
    }

    public PreProcessamentoImagem(String HaarFile, boolean ConverteEscalaCinza, boolean TrataHistogram) throws IOException {
        this.Inicializar(HaarFile, ConverteEscalaCinza, TrataHistogram, null);
    }

    public PreProcessamentoImagem(String HaarFile, boolean ConverteEscalaCinza) throws IOException {
        this.Inicializar(HaarFile, ConverteEscalaCinza, true, null);
       
    }

    public PreProcessamentoImagem(String HaarFile) throws IOException {
        this.Inicializar(HaarFile, true, true, null);
    }
    
    public void setDirSaida(String diretorio) throws Exception{
        
        File f = new File(diretorio);
        
        if(!f.isDirectory())
            throw new Exception("Invalid directory");
        
        if(!f.exists())
            f.mkdirs();
        
        
        this.dirSaida = f;
    }
    
    private void Inicializar(String HaarFile, boolean ConverteEscalaCinza, boolean TrataHistogram, float[][] matrizKernel) throws IOException{
        this.arquivoHaar = HaarFile;
        if(!(new File(this.arquivoHaar).exists())) throw new IOException("Haar file not found or invalid");
        this.ConverteEscalaCinza = ConverteEscalaCinza;
        this.TrataHistogram = TrataHistogram;
        this.facesEncontradas = null;
        this.dirSaida = null;
        this.matrizKernel = matrizKernel;
    }
    
    /**
     * Este metodo realiza a equalizacao de histogram de uma imagem em escala de niveis cinza com 8 bits
     * @param imagem A matriz
     * @return 
     */
    private Mat EqualizaHist(Mat imagem){
        
        equalizeHist(imagem, imagem);
        
        return imagem;
        
    }
    
    private Mat Convolucao(Mat imagemMat){
        
        Mat dest = new Mat(imagemMat.rows(), imagemMat.cols(), imagemMat.type());
    
        if(matrizKernel==null){
            matrizKernel = new float[][]{
                { 0,-1, 0 },
                {-1, 5,-1 },
                { 0,-1, 0 }
            };
        }
        
        
        int dim = matrizKernel.length;
        
        CvMat kernel = CvMat.create(dim, dim, CV_32F);
        
        for(int y=0; y<dim; y++)
            for(int x=0; x<dim; x++)
                kernel.put(y, x, matrizKernel[y][x]);
        
        Mat kernelMat = new Mat(kernel);
        filter2D(imagemMat, dest, -1, kernelMat);
        
        imagemMat.release();
        kernelMat.release();
        kernel.release();
        
        return dest;
        
    }
    
    private Mat ConvEscalaCinza(Mat mat){
        
        cvtColor(mat, mat, CV_BGR2GRAY);
        
        return mat;
    }
    
    private Mat Redimensionar(Mat imagemMat, int dimensaoMax){
        
        int largura, altura;
        
        if(imagemMat.cols()>imagemMat.rows()){
            largura = dimensaoMax;
            altura = (int)( (imagemMat.rows()*largura)/imagemMat.cols());
        }else{
            altura = dimensaoMax;
            largura = (int)( (imagemMat.cols()*altura)/imagemMat.rows());
        }

        Mat tmp = new Mat(altura, largura, imagemMat.type());
        
        resize(imagemMat, tmp, new Size(largura, altura));
        
        imagemMat.release();
        
        return tmp;      
        
    }
    
    private CvHaarClassifierCascade CarregarClassificador(String arquivo) throws EArquivoInvalido {
        
        CvHaarClassifierCascade cvHaarClassifierCascade = cvLoadHaarClassifierCascade(arquivo, cvSize(0, 0) );
        
        if(cvHaarClassifierCascade==null)
            throw new EArquivoInvalido("Invalid Haar file");
        
        return cvHaarClassifierCascade;
        
    }
    
    private CvSeq detectarObjetosHaar(CvHaarClassifierCascade classificador, CvArr img){
        
        this.storage = cvCreateMemStorage(0);
        
        CvSeq cvSeqFaces = cvHaarDetectObjects(
                
                // Matrix of the type CV_8U containing an image where objects are detected.
                img, 
                
                // Haar classifier cascade (OpenCV 1.x API only).
                // It can be loaded from XML or YAML file using Load().
                // When the cascade is not needed anymore, release it using cvReleaseHaarClassifierCascade(&cascade).
                classificador, 
                
                // Vector of rectangles where each rectangle contains the detected object.
                this.storage, 
                
                // Parameter specifying how much the image size is reduced at each image scale.
                1.1, 
                
                // Parameter specifying how many neighbors each candidate rectangle should have to retain it.
                3,
                
                // Parameter with the same meaning for an old cascade as in the function cvHaarDetectObjects.
                // It is not used for a new cascade.
                0,//CV_HAAR_DO_CANNY_PRUNING, 
                
                // Minimum possible object size. Objects smaller than that are ignored.
                cvSize(200, 200), 
                
                // Maximum possible object size. Objects larger than that are ignored.
                cvSize(DIM_MAX, DIM_MAX)
            );
        
        
        
        return cvSeqFaces;
        
    }
    
    private Rect[] CvSeq2RectArray(CvSeq cvSeqFacesEncontradas){
        
        Rect[] rects = new Rect[cvSeqFacesEncontradas.total()];
        
        for(int i=0; i<cvSeqFacesEncontradas.total(); i++){
            rects[i] =  new Rect(cvGetSeqElem(cvSeqFacesEncontradas, i));
        }
        
        return rects;
        
    }
    
    private Mat[] CvSeq2MatArray(CvSeq cvSeqFacesEncontradas, Mat matImagem){
    
        Mat[] facesRetorno = new Mat[cvSeqFacesEncontradas.total()];
        
        Rect[] rects = CvSeq2RectArray(cvSeqFacesEncontradas);
        
        for(int i=0; i<cvSeqFacesEncontradas.total(); i++){
           
            IplImage iplImageFromMat = matImagem.asIplImage();
            
            cvSetImageROI(iplImageFromMat, rects[i].asCvRect());
            
            IplImage tmp = cvCreateImage(
                    cvSize(rects[i].width(), rects[i].height()),
                    iplImageFromMat.depth(), 
                    iplImageFromMat.nChannels()
            );
            
            cvCopy(iplImageFromMat, tmp);
            cvResetImageROI(iplImageFromMat);
            iplImageFromMat.release();
            
            facesRetorno[i] = new Mat(tmp.clone());
            tmp.release();
            
        }
        
        return facesRetorno;
        
    }
    
    public Mat[] ExtrairFaces(String arquivo) throws EArquivoInvalido{
        
        Mat imagem = new Mat(cvLoadImage(arquivo));
        
        return ExtrairFaces(imagem);
        
    }
    
    public Mat[] ExtrairFaces(File arquivo) throws EArquivoInvalido{
        
        Mat imagem = new Mat(cvLoadImage(arquivo.getAbsolutePath()));
        
        return ExtrairFaces(imagem);
        
    }
    
    public Mat[] ExtrairFaces(Mat imagem) throws EArquivoInvalido{
        
        try{
            
            if(dirSaida!=null)
                cvSaveImage(dirSaida.getAbsolutePath()+"\\original.jpg", imagem.asIplImage());
            
            if(imagem.cols()<DIM_MAX && imagem.rows()<DIM_MAX)
                imagem = Redimensionar(imagem, DIM_MAX );
            
            if(dirSaida!=null)
                cvSaveImage(dirSaida.getAbsolutePath()+"\\redimensionado.jpg", imagem.asIplImage());

            imagem = Convolucao(imagem);
            if(dirSaida!=null)
                cvSaveImage(dirSaida.getAbsolutePath()+"\\convolucao.jpg", imagem.asIplImage());

            if(this.ConverteEscalaCinza){
                imagem = ConvEscalaCinza(imagem);
                if(dirSaida!=null)
                    cvSaveImage(dirSaida.getAbsolutePath()+"\\escala-cinza.jpg", imagem.asIplImage());
            }

            if(this.ConverteEscalaCinza && this.TrataHistogram){
                imagem = EqualizaHist(imagem);
                if(dirSaida!=null)
                    cvSaveImage(dirSaida.getAbsolutePath()+"\\histogram.jpg", imagem.asIplImage());
            }

            this.classificadorCascata = CarregarClassificador(this.arquivoHaar);

            this.facesEncontradas = detectarObjetosHaar(this.classificadorCascata, imagem.asCvMat());

            Mat[] ret = CvSeq2MatArray(this.facesEncontradas, imagem);
            
            if(dirSaida!=null)
                for(int i=0; i<ret.length; i++)
                    cvSaveImage(dirSaida.getAbsolutePath()+"\\face-"+i+".jpg", ret[i].asIplImage());

            return ret;
            
        } finally{

            
            this.storage.release();
            this.classificadorCascata.release();
            imagem.release();
            
        }   
        
    }

    @Override
    protected void finalize() throws Throwable {
        
        
        
        super.finalize();
    }
    
    
            
    
}
